package com.xuqi.W3;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import android.net.TrafficStats;

public class W3 {
	///////////////////////////////////////////////////////////////////////////
	/// definition(s) of Constant(s) 
	
	// some error code(s)
	public final static int W3_ERR_NO_ERROR 						= 0;
	public final static int W3_ERR_METHOD_NOT_SUPPORTED 			= 1;
	public final static int W3_ERR_URL_NOT_CORRECT 					= 2;
	public final static int W3_ERR_HTTPS_SSL_ERROR					= 3;
	public final static int W3_ERR_IO_ERROR							= 4;
	public final static int W3_ERR_FILENAME_NOT_CORRECT				= 5;
	public final static int W3_ERR_REQUEST_CANCELLED				= 6;
	public final static int W3_ERR_INCOMPLETE_RESPONSE				= 7;
	public final static int W3_ERR_INNER_ERROR_PARAMETER_INVALID 	= 8;
	public final static int W3_ERR_INNER_ERROR_MAX_SIZE_TOO_SMALL 	= 9;
	public final static int W3_ERR_OTHER_ERROR						= 10;

	protected static final String[] W3_VALID_METHODS		= {"GET", "POST"}; // valid method
	
	protected static final int W3_RESPONSE_HEADER_MAX_SIZE	= 10 * 1024;		// 10KB
	protected static final int W3_RESPONSE_DATA_MIN_SIZE	= 100 * 1024;		// 100 KB
	protected static final int W3_RESPONSE_DATA_MAX_SIZE	= 1 * 1024 * 1024;	// 1MB
	protected static final int W3_READ_BUFFER_SIZE			= 150000;				// roughly 1.5KB

	protected static final int W3_CONNECT_TIMEOUT 	= 65 * 1000; // 65s
	protected static final int W3_READ_TIMEOUT 		= 65 * 1000; // 65s
	

	///////////////////////////////////////////////////////////////////////////
	/// definition(s) of Member(s)

	private W3Response					m_cResponse;
	private HttpURLConnection			m_cConnection;
	private IW3EventListener			m_cEventListener;
	private boolean						m_bCancelFlag;
	
	///////////////////////////////////////////////////////////////////////////
	/// definition(s) of Member(s)
	private String						m_strAccessLog;
	private int							m_iAccessLogStep = 0;
	private static BufferedOutputStream s_cBos = null;
	private static boolean				s_bUrlAccessLogFlag = false;
	private static boolean				s_bUrlAccessLogFlagFileChecked = false;
	private static int					s_iReqNum = 0;
	
	private static final String		W3_URL_ACCESS_LOG_FILE = "urlAccessLog.csv";
	private static final String		W3_URL_ACCESS_LOG_DIR = "/USER/RW/W3/";
	private static final String		W3_URL_ACCESS_LOG_FLAG_FILE = "urlAccessLogOn";
	private static final String		W3_URL_ACCESS_LOG_REQ_NUM_FILE = "reqNum";
	private static final int		W3_URL_ACCESS_LOG_STEP_NONE	= 0;
	// The all steps are  log serv / path / param / post or get / post data / resp code / resp content / error code / timestamp.
	private static final int		W3_URL_ACCESS_LOG_STEP_RESPCONTENT	= 7;
	///////////////////////////////////////////////////////////////////////////
	
	private static List<String>			s_lstDefaultHeaders;
	static {
		s_lstDefaultHeaders = new LinkedList<String>();
		s_lstDefaultHeaders.add("Connection: close");
		s_lstDefaultHeaders.add("Accept-Encoding: gzip;q=1.0, identity;q=0.5, *;q=0");
	}

	// PATCH: Android 2.2 (and prior version) "keep-alive" not working correctly.
	// NOTE: MUST make "URL.openConnection" is not called before this class is loaded.
	static {
		W3Log.d(String.format("PRE-SYSCONFIG: http.keepAlive = %s", System.getProperty("http.keepAlive")));
		W3Log.d(String.format("PRE-SYSCONFIG: http.maxConnections = %s", System.getProperty("http.maxConnections")));
		System.setProperty("http.keepAlive", "0");
		W3Log.d(String.format("SYSCONFIG: http.keepAlive = %s", System.getProperty("http.keepAlive")));
		W3Log.d(String.format("SYSCONFIG: http.maxConnections = %s", System.getProperty("http.maxConnections")));
	}
	// END-OF-PATCH
	
	
	// /////////////////////////////////////////////////////////////////////////
	// / definitions of static function for url access log.
	public static void setUrlAccessLogFlag(boolean bLog)
	{
		W3Log.i("setUrlAccessLogFlag: " + bLog);
		s_bUrlAccessLogFlag = bLog;
		s_bUrlAccessLogFlagFileChecked = true;
		try
		{
			String docomoDrive = StorageAdapter.getApkDrive().getAbsolutePath();
			String flagFilePath = docomoDrive + W3_URL_ACCESS_LOG_DIR + W3_URL_ACCESS_LOG_FLAG_FILE;
			String respNumFilePath = docomoDrive + W3_URL_ACCESS_LOG_DIR + W3_URL_ACCESS_LOG_REQ_NUM_FILE;
			File flagFile = new File(flagFilePath);
			File reqNumFile = new File(respNumFilePath);

			if (bLog)
			{
				File dirW3 = new File(docomoDrive + W3_URL_ACCESS_LOG_DIR);
				dirW3.mkdirs();

				if (!flagFile.exists())
				{
					flagFile.createNewFile();
				}

				if (!reqNumFile.exists())
				{
					reqNumFile.createNewFile();
					s_iReqNum = 0;
				}
				else
				{
					BufferedReader readBuff = new BufferedReader(new FileReader(reqNumFile));
					String strReqNum = readBuff.readLine();
					if (strReqNum != null && strReqNum.length() > 0)
					{
						s_iReqNum = Integer.parseInt(strReqNum);
					}
					readBuff.close();
				}

				boolean bLogFileExists = false;
				if (s_cBos == null)
				{
					String filePath = docomoDrive + W3_URL_ACCESS_LOG_DIR + W3_URL_ACCESS_LOG_FILE;
					File logFile = new File(filePath);
					bLogFileExists = logFile.exists();
					FileOutputStream fos = new FileOutputStream(logFile, true);
					s_cBos = new BufferedOutputStream(fos);
				}

				if (s_cBos != null && !bLogFileExists)
				{
					String header = "serv,path,param,get/post,post data,response code,response content,error,timestamp";
					s_cBos.write(header.getBytes());
					s_cBos.flush();
				}
			}
			else
			{
				flagFile.delete();

				if (s_cBos != null)
				{
					s_cBos.close();
					s_cBos = null;
				}
			}
		} catch (FileNotFoundException e)
		{
			W3Log.i("Can not create w3 url access log file.");
		} catch (IOException e)
		{
			W3Log.i("IOException when write/close w3 url access log file.");
		}
	}

	public static boolean getUrlAccessLogFlag()
	{
		if (!s_bUrlAccessLogFlagFileChecked)
		{
			checkUrlAccessLogFlagFile();
		}
		return s_bUrlAccessLogFlag;
	}

	// /////////////////////////////////////////////////////////////////////////
	// / definitions of static function for getting traffic bytes.
	public static long getMobileRxBytes()
	{
		return TrafficStats.getMobileRxBytes();
	}

	public static long getMobileTxBytes()
	{
		return TrafficStats.getMobileTxBytes();
	}

	// /////////////////////////////////////////////////////////////////////////
	// / definition(s) of member Function(s)

	/**
	 * constructor
	 */
	public W3()
	{
		clearW3();
	}

	/**
	 * initialize
	 */
	public void initW3()
	{
		clearW3();
		m_cResponse = new W3Response();
		m_strAccessLog = "";
	}

	/**
	 * clear
	 */
	public void clearW3()
	{
		m_cResponse = null;
		m_cConnection = null;
		m_cEventListener = null;
		m_bCancelFlag = false;
	}

	/**
	 * get reponse
	 * 
	 * @return W3Response
	 */
	public W3Response getResponse()
	{
		return m_cResponse;
	}

	/**
	 * set OnW3DownloadProgressListener
	 * 
	 * @param cListener
	 *            [in]: OnW3DownloadProgressListener
	 */
	public void setW3DownloadProgressListener(IW3DownloadEventListener cListener)
	{
		m_cEventListener = cListener;
	}

	/**
	 * Set up/clear event listener for catching download progress events.
	 * 
	 * @param cListener
	 *            [IN] : the event listener;
	 */
	public void setEventListener(IW3EventListener cListener)
	{
		m_cEventListener = cListener;
	}

	/**
	 * set response code
	 * 
	 * @param iResCode
	 *            : response code
	 */
	protected void setResponseCode(int iResponseCode)
	{
		m_cResponse.setResponseCode(iResponseCode);
	}

	/**
	 * set response data
	 * 
	 * @param byResData
	 *            : response data
	 */
	protected void setResponseData(byte[] byResponseData)
	{
		m_cResponse.setResponseData(byResponseData);
	}

	/**
	 * set response header(s)
	 * 
	 * @param szResHeades
	 *            : response header(s)
	 */
	protected void setHeaderFields(String szHeaderFields)
	{
		m_cResponse.setHeaderFields(szHeaderFields);
	}

	/**
	 * set flag saved to file
	 * 
	 * @param bSaveToFile
	 */
	protected void setSaveToFile(boolean bSaveToFile)
	{
		m_cResponse.setSaveToFile(bSaveToFile);
	}

	/**
	 * set response file name
	 * 
	 * @param szFileName
	 */
	protected void setResponseFileName(String szFileName)
	{
		m_cResponse.setResponseFileName(szFileName);
	}

	/**
	 * set response size
	 * 
	 * @param iDataSize
	 */
	protected void setResponseSize(int iDataSize)
	{
		m_cResponse.setResponseSize(iDataSize);
	}

	/**
	 * set cancel flag
	 * 
	 * @param bCancelFlag
	 *            : cancel flag
	 */
	public void setCancelFlag(boolean bCancelFlag)
	{
		m_bCancelFlag = bCancelFlag;
	}

	/**
	 * get cancel flag
	 * 
	 * @return cancel flag
	 */
	public boolean getCancelFlag()
	{
		return m_bCancelFlag;
	}

	/**
	 * Create a new W3 instance.
	 * 
	 * @return the newly created instance.
	 */
	public static W3 createInstance()
	{
		return new W3();
	}

	public int doW3Action(W3Request cRequest)
	{
		W3Log.d("doW3Action +++");

		m_strAccessLog = "";
		m_iAccessLogStep = 0;
		String strTimestamp = "";

		if (!s_bUrlAccessLogFlagFileChecked)
		{
			checkUrlAccessLogFlagFile();
		}

		if (s_bUrlAccessLogFlag)
		{
			s_iReqNum++;
			SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
			strTimestamp += (",\"" + dateFormat.format(new Date()) + "\"");
		}

		int iErrCode = W3_ERR_NO_ERROR;

		if (null == cRequest)
		{
			W3Log.e("null == cRequest");
			iErrCode = W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		// check the method
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			if (!w3aIsMethodValid(cRequest.getMethod()))
			{
				W3Log.e("Method is not specified or invalid");
				iErrCode = W3_ERR_METHOD_NOT_SUPPORTED;
			}
		}

		// Action: Opens a connection
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			W3Log.d("URL: " + cRequest.getURL());

			try
			{
				URL cUrl = new URL(cRequest.getURL()); // may throw
														// MalformedURLException
				iErrCode = w3aOpenConnection(cUrl);
			} catch (MalformedURLException e)
			{
				W3Log.e("MalformedURLException: " + e.getMessage());
				iErrCode = W3_ERR_URL_NOT_CORRECT;
			}
		}

		// Set common parameter(s)
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aSetCommonParameters();
		}

		// Set post or get parameter(s)
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aSetPostOrGetParameters(cRequest);
		}

		// Set headers
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aSetHeaders(cRequest);
		}

		// Connect to server
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aConnect();
		}

		// Send request body
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aSetBody(cRequest);
		}

		// Get response code
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aGetResCode();
		}

		// Get response header(s)
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aGetResHeaders();
		}

		// Get response body
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aGetResBody(cRequest);
		}

		// Action: Disconnect
		if (W3_ERR_NO_ERROR == iErrCode)
		{
			iErrCode = w3aDisconnect();
		}
		else
		{
			// disconnect from server anyway.
			w3aDisconnect();
		}

		if (m_cEventListener instanceof IW3DownloadDataListener)
		{
			((IW3DownloadDataListener) m_cEventListener).onRecvFinish(iErrCode, m_cResponse.getResponseSize());
		}

		W3Log.d("doW3Action ---");
		// Add error code and time stamp to log.
		if (s_bUrlAccessLogFlag)
		{
			int paddingCount = W3_URL_ACCESS_LOG_STEP_RESPCONTENT - m_iAccessLogStep;
			for (int i = 0; i < paddingCount; i++)
			{
				m_strAccessLog += (",\"\"");
				m_iAccessLogStep += 1;
			}

			m_strAccessLog += (String.format(",\"%d\"", iErrCode));
			m_iAccessLogStep += 1;
			m_strAccessLog += strTimestamp;
			m_iAccessLogStep += 1;
			saveUrlAccessLog(m_strAccessLog);
		}

		return iErrCode;
	}

	/**
	 * W3Action function: check if method valid
	 * 
	 * @param szMethod
	 *            : method string
	 * @return : whether method is valid
	 */
	protected boolean w3aIsMethodValid(String szMethod)
	{
		if (null == szMethod)
		{
			W3Log.e("null == szMethod");
			return false;
		}

		boolean bValidMethod = false;
		for (int iIndex = 0; iIndex < W3.W3_VALID_METHODS.length; ++iIndex)
		{
			if (0 == szMethod.compareTo(W3.W3_VALID_METHODS[iIndex]))
			{
				bValidMethod = true;
				break;
			}
		}

		return bValidMethod;
	}

	/**
	 * W3Action function: open a connection
	 * 
	 * @param cUrl
	 *            : URL object
	 * @return : W3 error code
	 */
	protected int w3aOpenConnection(URL cUrl)
	{
		int iResult = W3.W3_ERR_NO_ERROR; // define return value

		// Add server url and parameter to log.
		saveServAndParamToLog(cUrl);

		try
		{
			if (cUrl.getProtocol().equals("https"))
			{
				HttpsURLConnection conn = (HttpsURLConnection) cUrl.openConnection();
				// TODO: use static object instead?
				// SSLContext sc = SSLContext.getInstance("TLS");
				// sc.init(null, new TrustManager[]{new W3TrustManager()},
				// null);
				// conn.setSSLSocketFactory(sc.getSocketFactory());
				// conn.setSSLSocketFactory(new W3SSLSocketFactory());
				conn.setHostnameVerifier(new W3HostnameVerifier());
				HttpsURLConnection.setDefaultSSLSocketFactory(new W3SSLSocketFactory());
				m_cConnection = conn;
			}
			else
			{
				m_cConnection = (HttpURLConnection) cUrl.openConnection();
			}
		} catch (NoSuchAlgorithmException e)
		{
			W3Log.e("NoSuchAlgorithmException: " + e.getMessage());
			iResult = W3.W3_ERR_HTTPS_SSL_ERROR;
		} catch (KeyManagementException e)
		{
			W3Log.e("KeyManagementException: " + e.getMessage());
			iResult = W3.W3_ERR_HTTPS_SSL_ERROR;
		} catch (IOException e)
		{
			W3Log.e("IOException: " + e.getMessage());
			iResult = W3.W3_ERR_IO_ERROR;
		}

		return iResult;
	}

	/**
	 * W3Action function: set common parameter(s)
	 * 
	 * @return : W3 error code
	 */
	protected int w3aSetCommonParameters()
	{
		if (null == m_cConnection)
		{
			W3Log.e("null == m_cConnection");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		W3Log.d("setConnectTimeout: " + W3.W3_CONNECT_TIMEOUT);
		m_cConnection.setConnectTimeout(W3.W3_CONNECT_TIMEOUT);

		W3Log.d("setReadTimeout: " + W3.W3_READ_TIMEOUT);
		m_cConnection.setReadTimeout(W3.W3_READ_TIMEOUT);

		return W3.W3_ERR_NO_ERROR;
	}

	/**
	 * W3Action function: set post or get parameter(s)
	 * 
	 * @param cParam
	 *            : W3Connection parameter
	 * @return : W3 error code
	 */
	protected int w3aSetPostOrGetParameters(W3Request cRequest)
	{
		if (null == cRequest)
		{
			W3Log.e("null == cRequest");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		W3Log.d("Method: " + cRequest.getMethod());

		if (s_bUrlAccessLogFlag)
		{
			m_strAccessLog += (",\"" + cRequest.getMethod() + "\"");
			m_iAccessLogStep += 1;
		}

		try
		{
			m_cConnection.setRequestMethod(cRequest.getMethod()); // May throw
																	// protocol
																	// exception
		} catch (ProtocolException e)
		{
			W3Log.e("ProtocolException: " + e.getMessage());
			return W3.W3_ERR_METHOD_NOT_SUPPORTED;
		}

		// Set stream as an input/ouput stream
		if (cRequest.getMethod().equals("POST"))
		{
			m_cConnection.setDoInput(true);
			m_cConnection.setDoOutput(true);
			m_cConnection.setUseCaches(false);
		}
		else
		{
			m_cConnection.setDoInput(true);
		}

		return W3.W3_ERR_NO_ERROR;
	}

	/**
	 * W3Action function: set header parameter(s)
	 * 
	 * @param szHeaders
	 *            : header string
	 * @return : W3 error code
	 */
	protected int w3aSetHeaders(W3Request cRequest)
	{
		if (null == cRequest)
		{
			W3Log.e("null == cRequest");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		if (null == m_cConnection)
		{
			W3Log.e("null == m_cConnection");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		if (W3Request.W3_REQ_SEND_HEADER_FLAG_NONE == cRequest.getSendHeaderFlag())
		{
			W3Log.d("no headers to set");
		}
		else
		{
			String[] allHeaders = cRequest.getSendHeaders().split("\r\n");
			for (int iHeadIdx = 0; iHeadIdx < allHeaders.length; ++iHeadIdx)
			{
				allHeaders[iHeadIdx] = allHeaders[iHeadIdx].trim();
				if (0 == allHeaders[iHeadIdx].length())
				{
					continue;
				}

				int iCommaPos = allHeaders[iHeadIdx].indexOf(':');
				if (iCommaPos <= 0)
				{
					W3Log.e("Invalid Header Line : " + allHeaders[iHeadIdx]);
					continue;
				}

				String strName = allHeaders[iHeadIdx].substring(0, iCommaPos).trim();
				String strValue = allHeaders[iHeadIdx].substring(iCommaPos + 1).trim();

				W3Log.d(String.format("setRequestProperty %s: %s", strName, strValue));

				m_cConnection.setRequestProperty(strName, strValue);
			}
		}
		{
			List<String> lstAgent = null;
			Map<String, List<String>> mapHdrs = m_cConnection.getRequestProperties();
			if (null != mapHdrs)
			{
				lstAgent = mapHdrs.get("user-agent");
			}
			if (null == lstAgent || lstAgent.isEmpty())
			{
				W3Log.d("setRequestProperty User-Agent: 12USAAA/1.000000");
				m_cConnection.setRequestProperty("User-Agent", "12USAAA/1.000000");
			}

			Iterator<String> it = s_lstDefaultHeaders.iterator();
			while (it.hasNext())
			{
				String str = it.next();

				int iCommaPos = str.indexOf(':');
				if (iCommaPos <= 0)
				{
					W3Log.e("Invalid Default Request Header: " + str);
					continue;
				}

				String strName = str.substring(0, iCommaPos).trim();
				String strValue = str.substring(iCommaPos + 1).trim();

				W3Log.d(String.format("setRequestProperty %s: %s", strName, strValue));

				m_cConnection.setRequestProperty(strName, strValue);
			}
		}

		return W3.W3_ERR_NO_ERROR;
	}

	/**
	 * W3Action function: set body parameter(s)
	 * 
	 * @param cParam
	 *            : W3Connection parameter
	 * @return : W3 error code
	 */
	protected int w3aSetBody(W3Request cRequest)
	{
		if (null == cRequest)
		{
			W3Log.e("null == cRequest");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		if (null == m_cConnection)
		{
			W3Log.e("null == m_cConnection");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		String strDebugPostDataFile = "";
		if (s_bUrlAccessLogFlag)
		{
			m_strAccessLog += ",\"";

			String postFileName = String.format("post_%d", s_iReqNum);
			if (cRequest.getMethod().equals("POST"))
			{
				String docomoDrive = StorageAdapter.getApkDrive().getAbsolutePath();
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH");
				String subDir = dateFormat.format(new Date()) + "/";
				String strDir = docomoDrive + W3_URL_ACCESS_LOG_DIR + subDir;
				File dir = new File(strDir);
				dir.mkdirs();
				strDebugPostDataFile = strDir + postFileName;
				m_strAccessLog += (subDir + postFileName);
			}
			m_strAccessLog += "\"";
			m_iAccessLogStep += 1;
		}

		if (cRequest.getMethod().equals("POST"))
		{
			int iSendDataFlag = cRequest.getSendDataFlag();
			if (W3Request.W3_REQ_SEND_DATA_FLAG_NONE == iSendDataFlag)
			{
				W3Log.i("No data to POST");
			}
			else if (W3Request.W3_REQ_SEND_DATA_FLAG_FROM_BUFFER == iSendDataFlag)
			{
				W3Log.i("SEND DATA: " + cRequest.getSendData());
				try
				{
					OutputStream out = m_cConnection.getOutputStream();
					out.write(cRequest.getSendData());
				} catch (IOException e)
				{
					W3Log.e("IOException: " + e.getMessage());
					return W3.W3_ERR_IO_ERROR;
				}

				if (s_bUrlAccessLogFlag)
				{
					try
					{
						FileOutputStream fos = new FileOutputStream(strDebugPostDataFile, false);
						fos.write(cRequest.getSendData());
						fos.flush();
						fos.close();
					} catch (IOException e)
					{
						W3Log.e("error(debug): " + e.toString());
					}
				}
			}
			else
			{ // W3Request.W3_REQ_SEND_DATA_FLAG_FROM_FILE
				BufferedInputStream bufferedIn = null;
				try
				{
					bufferedIn = new BufferedInputStream(new FileInputStream(cRequest.getSendFileName()));
				} catch (FileNotFoundException e)
				{
					W3Log.e("FileNotFoundException: " + e.getMessage());
					return W3.W3_ERR_IO_ERROR;
				}

				try
				{
					byte[] byReadBuffer = new byte[W3.W3_READ_BUFFER_SIZE];
					OutputStream out = m_cConnection.getOutputStream();

					FileOutputStream fos_log = null;
					if (s_bUrlAccessLogFlag)
					{
						try
						{
							fos_log = new FileOutputStream(strDebugPostDataFile, false);
						} catch (IOException e)
						{
							W3Log.e("error(debug): " + e.toString());
						}
					}

					int iReadBytes = 0;
					while (true)
					{
						iReadBytes = bufferedIn.read(byReadBuffer);
						if (-1 == iReadBytes)
						{
							break;
						}

						out.write(byReadBuffer, 0, iReadBytes);

						if (fos_log != null)
						{
							try
							{
								fos_log.write(byReadBuffer, 0, iReadBytes);
							} catch (IOException e)
							{
								W3Log.e("error(debug): " + e.toString());
							}
						}
					}
					byReadBuffer = null;

					if (fos_log != null)
					{
						try
						{
							fos_log.flush();
							fos_log.close();
						} catch (IOException e)
						{
							W3Log.e("error(debug): " + e.toString());
						}
					}
				} catch (IOException e)
				{
					W3Log.e("IOException: " + e.getMessage());
					return W3.W3_ERR_IO_ERROR;
				}
			}
		}

		return W3.W3_ERR_NO_ERROR;
	}

	/**
	 * W3Action function: connect an established connection
	 * 
	 * @return : W3 error code
	 */
	protected int w3aConnect()
	{
		if (null == m_cConnection)
		{
			W3Log.e("null == m_cConnection");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		try
		{
			m_cConnection.connect();
		} catch (IOException e)
		{
			W3Log.e("IOException: " + e.getMessage());
			return W3.W3_ERR_IO_ERROR;
		} catch (ArrayIndexOutOfBoundsException e)
		{
			/**
			 * Android 2.3 BUG WORKAROUND: - For HTTPS connections, it may throw
			 * ArrayIndexOutOfBoundsException when generating random number. -
			 * Auto-retry will be executed for most connections, so it's safe to
			 * simply ignore the error here.
			 */
			W3Log.e("IOException: " + e.getMessage());
			return W3.W3_ERR_IO_ERROR;
		}

		return W3.W3_ERR_NO_ERROR;
	}

	/**
	 * W3Action function: get response code
	 * 
	 * @return : W3 error code
	 */
	protected int w3aGetResCode()
	{
		if (null == m_cConnection)
		{
			W3Log.e("Error: null == m_cConnection");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		try
		{
			int iResCode = m_cConnection.getResponseCode();
			setResponseCode(iResCode); // save response code

			W3Log.d("Response Code: " + iResCode);

			if (s_bUrlAccessLogFlag)
			{
				m_strAccessLog += (",\"" + iResCode + "\"");
				m_iAccessLogStep += 1;
			}
		} catch (IOException e)
		{
			W3Log.e("IOException: " + e.getMessage());
			return W3.W3_ERR_IO_ERROR;
		}

		return W3.W3_ERR_NO_ERROR;
	}

	/**
	 * W3Action function: get response header(s)
	 * 
	 * @return : W3 error code
	 */
	protected int w3aGetResHeaders()
	{
		int iErrCode = W3_ERR_NO_ERROR;
		Map<String, List<String>> mapHeaders = null;

		if (null == m_cConnection)
		{
			W3Log.e("Error: null == m_cConnection");
			iErrCode = W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		if (W3_ERR_NO_ERROR == iErrCode)
		{
			mapHeaders = m_cConnection.getHeaderFields();
			if (mapHeaders.size() <= 0)
			{
				W3Log.i("No Response Headers Record");
			}

			int iHttpStatus = m_cResponse.getResponseCode();
			int iExpectedLength = w3aGetContentLength();
			if (m_cEventListener instanceof IW3DownloadEventListener)
			{
				((IW3DownloadEventListener) m_cEventListener).onRecvHeader(iHttpStatus, iExpectedLength, m_cConnection.getContentType(),
						m_cConnection.getContentEncoding());
			}
			if (m_cEventListener instanceof IW3DownloadDataListener)
			{
				if (!((IW3DownloadDataListener) m_cEventListener).onRecvHeader(iHttpStatus, iExpectedLength))
				{
					iErrCode = W3_ERR_REQUEST_CANCELLED;
				}
			}
		}

		if (W3_ERR_NO_ERROR == iErrCode)
		{
			StringBuffer strHeaders = new StringBuffer();
			Iterator<Map.Entry<String, List<String>>> itor = mapHeaders.entrySet().iterator();
			while ((W3_ERR_NO_ERROR == iErrCode) && itor.hasNext())
			{
				Map.Entry<String, List<String>> entry = itor.next();
				String szKey = entry.getKey();
				List<String> valueList = entry.getValue();

				Iterator<String> valueItor = valueList.iterator();
				while ((W3_ERR_NO_ERROR == iErrCode) && valueItor.hasNext())
				{
					String szValue = valueItor.next();

					strHeaders.append(szKey);
					strHeaders.append(":");
					strHeaders.append(szValue);
					strHeaders.append("\r\n");

					W3Log.d(String.format("Response Header: <%s>:<%s>", szKey, szValue));
					if (m_cEventListener instanceof IW3DownloadDataListener)
					{
						if (!((IW3DownloadDataListener) m_cEventListener).onRecvHeader(szKey, szValue))
						{
							iErrCode = W3_ERR_REQUEST_CANCELLED;
						}
					}

					if ((W3_ERR_NO_ERROR == iErrCode) && (strHeaders.length() > W3.W3_RESPONSE_HEADER_MAX_SIZE))
					{
						W3Log.e("Error: Response Header Buffer is too small");
						iErrCode = W3_ERR_INNER_ERROR_MAX_SIZE_TOO_SMALL;
					}
				}
			}

			if (W3_ERR_NO_ERROR == iErrCode)
			{
				setHeaderFields(strHeaders.toString());
			}
		}

		return iErrCode;
	}

	/**
	 * W3Action function: get response data
	 * 
	 * @param cParam
	 *            : W3Connection parameter
	 * @return : W3 error code
	 */
	protected int w3aGetResBody(W3Request cRequest)
	{
		if (null == cRequest)
		{
			W3Log.e("null == cRequest");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		int iResult = W3.W3_ERR_NO_ERROR;
		int iReceiveDataFlag = cRequest.getReceiveDataFlag();
		OutputStream cOutputStream = null;
		if (W3Request.W3_REQ_RECEIVE_DATA_FLAG_NONE == iReceiveDataFlag)
		{
			W3Log.i("receive no data");
		}
		else if (W3Request.W3_REQ_RECEIVE_DATA_FLAG_TO_FILE == iReceiveDataFlag)
		{
			String szFileName = cRequest.getReceiveFileName();

			setSaveToFile(true);

			if ((null == szFileName) || (szFileName.length() <= 0))
			{
				W3Log.e("Error: filename not correct");
				iResult = W3.W3_ERR_FILENAME_NOT_CORRECT;
			}
			else
			{
				// open the file
				try
				{
					cOutputStream = new BufferedOutputStream(new FileOutputStream(szFileName, false));
					setResponseFileName(szFileName);
				} catch (FileNotFoundException e)
				{
					W3Log.e("FileNotFoundException: " + e.getMessage());
					iResult = W3.W3_ERR_FILENAME_NOT_CORRECT;
				}
			}
		}
		else if (m_cEventListener instanceof IW3DownloadDataListener)
		{
			setSaveToFile(false);

			// no output stream, because m_cEventListener will handle it.
		}
		else
		{
			int iContentLength = w3aGetContentLength();
			setSaveToFile(false);
			if (-1 == iContentLength)
			{
				cOutputStream = new ByteArrayOutputStream(W3_RESPONSE_DATA_MIN_SIZE);
			}
			else if (iContentLength <= W3_RESPONSE_DATA_MAX_SIZE)
			{
				cOutputStream = new ByteArrayOutputStream(iContentLength);
			}
			else
			{
				W3Log.e("Error: Response data buffer is too small");
				iResult = W3.W3_ERR_INNER_ERROR_MAX_SIZE_TOO_SMALL;
			}
		}

		iResult = w3aGetResBody(cOutputStream);
		if (null != cOutputStream)
		{
			try
			{
				cOutputStream.flush();
				cOutputStream.close();
			} catch (IOException e)
			{
				W3Log.e("IOException: " + e.getMessage());
				iResult = W3.W3_ERR_IO_ERROR;
			}
		}

		return iResult;
	}

	/**
	 * W3Action function: disconnect a connection
	 * 
	 * @return : W3 error code
	 */
	protected int w3aDisconnect()
	{
		if (null == m_cConnection)
		{
			W3Log.e("Error: null == m_cConnection");
			return W3.W3_ERR_INNER_ERROR_PARAMETER_INVALID;
		}

		m_cConnection.disconnect();
		m_cConnection = null;

		return W3.W3_ERR_NO_ERROR;
	}

	// /////////////////////////////////////////////////////////////////////////
	// / private functions

	/**
	 * Get expected HTTP body returned from server.
	 * 
	 * @return Gets the content length in bytes specified by the response header
	 *         field content-length or -1 if this field is not set.
	 */
	private int w3aGetContentLength()
	{
		int iLength = -1;

		if (null == m_cConnection)
		{
			W3Log.e("Error: null == m_cConnection");
		}
		else
		{
			String strEncoding = m_cConnection.getContentEncoding();
			if ((null == strEncoding) || strEncoding.toLowerCase().equals("identity"))
			{
				// non-compressing data.
				iLength = m_cConnection.getContentLength();
			}
			else
			{
				// compressed data, impossible to predict the length
			}
		}

		return iLength;
	}

	/**
	 * W3Action function: get response data and save into an output stream.
	 * 
	 * @param cOutputStream
	 *            : the output stream to hold received data.
	 * @return : W3 error code
	 */
	private int w3aGetResBody(OutputStream cOutputStream)
	{
		int iResult = W3.W3_ERR_NO_ERROR; // return value

		try
		{
			byte[] byReadBuffer = new byte[W3_READ_BUFFER_SIZE]; // read data
																	// buffer
			W3HttpInputStream cInputStream = new W3HttpInputStream(m_cConnection.getInputStream(), m_cConnection.getContentEncoding());

			boolean bAccessLog_SaveContent = false;
			BufferedOutputStream bosDebug = null;
			if (s_bUrlAccessLogFlag)
			{
				// String header = m_cResponse.getHeaderFields();
				// String textContentPattern =
				// "(.*\\r?\\n)*.*[C|c]ontent-[Tt]ype\\s*:\\s*text/.*(\\r?\\n.*)*";
				// bAccessLog_SaveContent = header.matches(textContentPattern);
				bAccessLog_SaveContent = true;
				String strReqNum = String.format("resp_%d", s_iReqNum);
				String docomoDrive = StorageAdapter.getApkDrive().getAbsolutePath();
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH");
				String subDir = dateFormat.format(new Date()) + "/";
				String strDir = docomoDrive + W3_URL_ACCESS_LOG_DIR + subDir;

				String respFilePath = strDir + strReqNum;
				String reqNumFilePath = docomoDrive + W3_URL_ACCESS_LOG_DIR + W3_URL_ACCESS_LOG_REQ_NUM_FILE;
				File respDir = new File(strDir);
				respDir.mkdirs();
				try
				{
					FileOutputStream fosReqNum = new FileOutputStream(reqNumFilePath, false);
					if (fosReqNum != null)
					{
						fosReqNum.write(Integer.toString(s_iReqNum).getBytes());
						fosReqNum.close();
					}
					bosDebug = new BufferedOutputStream(new FileOutputStream(respFilePath, false));
				} catch (IOException e)
				{
					W3Log.e("Error(debug): " + e.toString());
				}

				m_strAccessLog += (",\"");
				m_strAccessLog += subDir + strReqNum;
				m_strAccessLog += ("\"");
				m_iAccessLogStep += 1;
			}

			int iDataSize = 0; // Total data size

			// read data to data buffer
			int iReadBytes = 0;
			while (-1 != iReadBytes)
			{
				iReadBytes = cInputStream.read(byReadBuffer);

				// read data complete
				if (-1 == iReadBytes)
				{
					break;
				}

				// check if request cancelled
				if (true == getCancelFlag())
				{
					iResult = W3.W3_ERR_REQUEST_CANCELLED;
					break;
				}

				// save received data
				if (m_cEventListener instanceof IW3DownloadDataListener)
				{
					if (!((IW3DownloadDataListener) m_cEventListener).onRecvData(byReadBuffer, iReadBytes))
					{
						iResult = W3.W3_ERR_REQUEST_CANCELLED;
						break;
					}
				}
				if (cOutputStream instanceof ByteArrayOutputStream)
				{
					/* avoid using up all available memory */
					if ((iDataSize + iReadBytes) > W3_RESPONSE_DATA_MAX_SIZE)
					{
						W3Log.e("Error: Response data buffer is too small");
						iResult = W3.W3_ERR_INNER_ERROR_MAX_SIZE_TOO_SMALL;
						break;
					}
				}
				if (null != cOutputStream)
				{
					cOutputStream.write(byReadBuffer, 0, iReadBytes);
				}

				if (bAccessLog_SaveContent && bosDebug != null)
				{
					try
					{
						bosDebug.write(byReadBuffer, 0, iReadBytes);
						bosDebug.flush();
					} catch (IOException e)
					{
						W3Log.e("Error(debug): " + e.toString());
					}
				}

				iDataSize += iReadBytes;

				// OnDownloadProgressListener
				if (m_cEventListener instanceof IW3DownloadEventListener)
				{
					String szFileNameNoPath = null;
					String szFileName = m_cResponse.getResponseFileName();

					if (null != szFileName)
					{
						int iIndexStart = szFileName.lastIndexOf("/");
						if (-1 == iIndexStart)
						{
							szFileNameNoPath = szFileName;
						}
						else
						{
							szFileNameNoPath = szFileName.substring(iIndexStart + 1);
						}
					}

					if (null == szFileNameNoPath)
					{
						W3Log.d(String.format("Download Progress: name: null, size: %d", iDataSize));
					}
					else
					{
						W3Log.d(String.format("Download Progress: name: %s, size: %d", szFileNameNoPath, iDataSize));
					}

					((IW3DownloadEventListener) m_cEventListener).onDownloadProgress(szFileNameNoPath, iDataSize);
				}
			}

			if (bosDebug != null)
			{
				try
				{
					bosDebug.flush();
					bosDebug.close();
				} catch (IOException e)
				{
					W3Log.e("Error(debug): " + e.toString());
				}
			}

			// save data
			if (W3.W3_ERR_NO_ERROR == iResult)
			{
				// NOTE: we need the size of raw data, so do NOT use
				// 'w3aGetContentLength' here.
				int iRawBytes = cInputStream.getReadRawBytes();
				int iContentLength = m_cConnection.getContentLength();
				if ((-1 != iContentLength) && (iContentLength != iRawBytes))
				{
					W3Log.e(String.format("Received data size (%d) does not match expected length (%d)", iRawBytes, iContentLength));
					iResult = W3.W3_ERR_INCOMPLETE_RESPONSE;
				}
				else
				{
					setResponseSize(iDataSize);
					if (cOutputStream instanceof ByteArrayOutputStream)
					{
						setResponseData(((ByteArrayOutputStream) cOutputStream).toByteArray());
					}
				}
			}
		} catch (IOException e)
		{
			W3Log.e("IOException: " + e.getMessage());
			iResult = W3.W3_ERR_IO_ERROR;
		}

		return iResult;
	}

	private void saveUrlAccessLog(String log)
	{
		saveUrlAccessLog(log.getBytes(), 0, log.getBytes().length);
	}

	private static synchronized void saveUrlAccessLog(byte[] bytes, int offset, int length)
	{
		if (!s_bUrlAccessLogFlag)
		{
			return;
		}
		try
		{
			if (s_cBos != null)
			{
				s_cBos.write(bytes, offset, length);
				s_cBos.flush();
			}
		} catch (IOException e)
		{
			W3Log.i("Can not write url access log to file");
		}
	}

	private void saveServAndParamToLog(URL cUrl)
	{
		if (s_bUrlAccessLogFlag)
		{
			String host = cUrl.getHost();
			String port = "default";
			int iPort = cUrl.getPort();
			if (iPort != -1)
			{
				port = Integer.toString(iPort);
			}
			String param = cUrl.getQuery();
			String servPath = cUrl.getPath();
			// new line
			m_strAccessLog += "\n";
			// server
			m_strAccessLog += ("\"" + host + ":" + port + "\"");
			m_iAccessLogStep += 1;
			// path
			m_strAccessLog += (",\"" + servPath + "\"");
			m_iAccessLogStep += 1;
			// param
			m_strAccessLog += (",\"" + param + "\"");
			m_iAccessLogStep += 1;
		}
	}

	private static void checkUrlAccessLogFlagFile()
	{
		String docomoDrive = StorageAdapter.getApkDrive().getAbsolutePath();
		String flagFilePath = docomoDrive + W3_URL_ACCESS_LOG_DIR + W3_URL_ACCESS_LOG_FLAG_FILE;
		File flagFile = new File(flagFilePath);
		s_bUrlAccessLogFlag = flagFile.exists();
		setUrlAccessLogFlag(s_bUrlAccessLogFlag);
		s_bUrlAccessLogFlagFileChecked = true;
	}

	// /////////////////////////////////////////////////////////////////////////
	// / definition(s) of inner Class(es)

	/**
	 * W3SSLSocketFactory
	 */
	private static class W3SSLSocketFactory extends SSLSocketFactory
	{
		private SSLSocketFactory m_cSSLSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();

		public W3SSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException
		{
			super();
			SSLContext sc = SSLContext.getInstance("TLS");
			sc.init(null, new TrustManager[]
			{ new W3TrustManager() }, new SecureRandom());
			m_cSSLSocketFactory = sc.getSocketFactory();
		}

		// private void configureSSLSocket(SSLSocket cSocket) {
		// cSocket.setEnabledCipherSuites(getDefaultCipherSuites());
		// }

		@Override
		public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException
		{
			SSLSocket cSocket = null;
			if (null != m_cSSLSocketFactory)
			{
				// cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(s,
				// host, port, autoClose);
				// configureSSLSocket(cSocket);
				cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(s, host, port, autoClose);
			}
			return cSocket;
		}

		@Override
		public String[] getDefaultCipherSuites()
		{
			// return new String[] { "AES256-SHA" };
			String[] cipherSuites = null;
			if (null != m_cSSLSocketFactory)
			{
				cipherSuites = m_cSSLSocketFactory.getDefaultCipherSuites();
			}
			return cipherSuites;
		}

		@Override
		public String[] getSupportedCipherSuites()
		{
			String[] aryCipherSuites = null;
			if (null != m_cSSLSocketFactory)
			{
				aryCipherSuites = m_cSSLSocketFactory.getSupportedCipherSuites();
			}
			return aryCipherSuites;
		}

		@Override
		public Socket createSocket(String host, int port) throws IOException, UnknownHostException
		{
			SSLSocket cSocket = null;
			if (null != m_cSSLSocketFactory)
			{
				// cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(host,
				// port);
				// configureSSLSocket(cSocket);
				cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(host, port);
			}
			return cSocket;
		}

		@Override
		public Socket createSocket(InetAddress host, int port) throws IOException
		{
			SSLSocket cSocket = null;
			if (null != m_cSSLSocketFactory)
			{
				// cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(host,
				// port);
				// configureSSLSocket(cSocket);
				cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(host, port);
			}
			return cSocket;
		}

		@Override
		public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException
		{
			SSLSocket cSocket = null;
			if (null != m_cSSLSocketFactory)
			{
				// cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(host,
				// port, localHost, localPort);
				// configureSSLSocket(cSocket);
				cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(host, port, localHost, localPort);
			}
			return cSocket;
		}

		@Override
		public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
		{
			SSLSocket cSocket = null;
			if (null != m_cSSLSocketFactory)
			{
				// cSocket = (SSLSocket)
				// m_cSSLSocketFactory.createSocket(address, port, localAddress,
				// localPort);
				// configureSSLSocket(cSocket);
				cSocket = (SSLSocket) m_cSSLSocketFactory.createSocket(address, port, localAddress, localPort);
			}
			return cSocket;
		}
	}

	/**
	 * W3TrustManager
	 */
	private static class W3TrustManager implements X509TrustManager
	{
		@Override
		public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException
		{
		}

		@Override
		public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException
		{
		}

		@Override
		public X509Certificate[] getAcceptedIssuers()
		{
			return new java.security.cert.X509Certificate[]
			{};
			// return null;
		}
	}

	/**
	 * W3HostnameVerifier
	 */
	private static class W3HostnameVerifier implements HostnameVerifier
	{
		@Override
		public boolean verify(String hostname, SSLSession session)
		{
			return true;
		}
	}

	/**
	 * Interface for receiving W3Lib event.
	 */
	public interface IW3EventListener
	{
	}

	/**
	 * IW3DownloadEventListener
	 * 
	 * @note Interface for receiving server response information before download
	 *       completes.
	 */
	public interface IW3DownloadEventListener extends IW3EventListener
	{
		/**
		 * onDownloadProgress
		 * 
		 * @note callback when download progress changed
		 * 
		 * @param iNowSize
		 *            [in]: size until now
		 */
		public void onDownloadProgress(String strFileName, int iFileNowSize);

		/**
		 * It's called when we have fully received a HTTP header from server.
		 * 
		 * @param iHttpStatus
		 *            [in] : the HTTP status code returned from server
		 * @param iExpectedLength
		 *            : the expected content length in bytes, or -1 if the
		 *            content length is unknown.
		 * @param strMIME
		 *            : MIME type returned from server.
		 * @param strEncoding
		 *            : content encoding returned from server.
		 * 
		 * @note Do *NOT* do time costly job inside the event handler, the
		 *       download will pause until the event handler returns.
		 */
		public void onRecvHeader(int iHttpStatus, int iExpectedLength, String strMIME, String strEncoding);
	}

	/**
	 * Interface for receiving data before download completes.
	 */
	public interface IW3DownloadDataListener extends IW3EventListener
	{
		/**
		 * It's called when HTTP headers are received from server.
		 * 
		 * @param iHttpStatus
		 *            : HTTP server status
		 * @param iExpectedLen
		 *            : expected response body length, -1 if unknown.
		 * 
		 * @return Return true if the task should be kept, return false if the
		 *         task should be canceled.
		 */
		public boolean onRecvHeader(int iHttpStatus, int iExpectedLength);

		/**
		 * It's called when HTTP headers are received from server.
		 * 
		 * @param pszName
		 *            [IN] : name of the response header.
		 * @param pszValue
		 *            [IN] : value of the response header.
		 * 
		 * @return Return true to allow further process, return false to cancel
		 *         further operations.
		 * 
		 * @note It'll be called multiple times, once for each response header.
		 */
		public boolean onRecvHeader(String strName, String strValue);

		/**
		 * It's called when any data from server is available.
		 * 
		 * @param pData
		 *            [IN] : data piece received from server.
		 * @param iDataSize
		 *            [IN] : size of the data piece in bytes.
		 * 
		 * @return Return true to allow further process, return false to cancel
		 *         further operations.
		 * 
		 * @note It's called ONLY when you request to save server response to
		 *       buffer. In such case, you MUST concatenate the contents of each
		 *       data piece delivered to build up the complete data for a URL
		 *       load.
		 * 
		 * @note You MUST copy the data delivered to this function to some where
		 *       else before return instead of keeping the data pointer.
		 */
		public boolean onRecvData(byte[] aryData, int iDataSize);

		/**
		 * It's called when previously received data must be discarded.
		 */
		public void onDiscardData();

		/**
		 * It's called when the request is finished, either successful or
		 * failed.
		 * 
		 * @param iErrCode
		 *            [IN] : error code, used to identify if the request has
		 *            been finished successfully by checking if it's zero.
		 * @param pszDesc
		 *            [IN] : error message if [iErrCode] is not zero.
		 * 
		 * @note Derived classes *MUST* reply the event to [HttpConnectionProxy]
		 *       when it's received.
		 */
		public void onRecvFinish(int iErrCode, int iLength);
	}

	/**
	 * Helper class for implementing IW3EventListener.
	 */
	public static abstract class W3DownloadEventListenerEx implements IW3DownloadEventListener, IW3DownloadDataListener
	{

		/**
		 * It's called when HTTP headers are received from server.
		 * 
		 * @param iHttpStatus
		 *            : HTTP server status
		 * @param iExpectedLen
		 *            : expected response body length, -1 if unknown.
		 * 
		 * @return Return true if the task should be kept, return false if the
		 *         task should be canceled.
		 */
		@Override
		public boolean onRecvHeader(int iHttpStatus, int iExpectedLength)
		{
			return true;
		}

		/**
		 * It's called when HTTP headers are received from server.
		 * 
		 * @param pszName
		 *            [IN] : name of the response header.
		 * @param pszValue
		 *            [IN] : value of the response header.
		 * 
		 * @return Return true to allow further process, return false to cancel
		 *         further operations.
		 * 
		 * @note It'll be called multiple times, once for each response header.
		 */
		@Override
		public boolean onRecvHeader(String strName, String strValue)
		{
			return true;
		}

		/**
		 * It's called when any data from server is available.
		 * 
		 * @param pData
		 *            [IN] : data piece received from server.
		 * @param iDataSize
		 *            [IN] : size of the data piece in bytes.
		 * 
		 * @return Return true to allow further process, return false to cancel
		 *         further operations.
		 * 
		 * @note It's called ONLY when you request to save server response to
		 *       buffer. In such case, you MUST concatenate the contents of each
		 *       data piece delivered to build up the complete data for a URL
		 *       load.
		 * 
		 * @note You MUST copy the data delivered to this function to some where
		 *       else before return instead of keeping the data pointer.
		 */
		@Override
		public abstract boolean onRecvData(byte[] aryData, int iDataSize);

		/**
		 * It's called when previously received data must be discarded.
		 */
		@Override
		public void onDiscardData()
		{
		}

		/**
		 * It's called when the request is finished, either successful or
		 * failed.
		 * 
		 * @param iErrCode
		 *            [IN] : error code, used to identify if the request has
		 *            been finished successfully by checking if it's zero.
		 * @param pszDesc
		 *            [IN] : error message if [iErrCode] is not zero.
		 * 
		 * @note Derived classes *MUST* reply the event to [HttpConnectionProxy]
		 *       when it's received.
		 */
		@Override
		public abstract void onRecvFinish(int iErrCode, int iLength);

		/**
		 * It's called when we have fully received a HTTP header from server.
		 * 
		 * @param iHttpStatus
		 *            [in] : the HTTP status code returned from server
		 * @param iExpectedLength
		 *            : the expected content length in bytes, or -1 if the
		 *            content length is unknown.
		 * @param strMIME
		 *            : MIME type returned from server.
		 * @param strEncoding
		 *            : content encoding returned from server.
		 * 
		 * @note Do *NOT* do time costly job inside the event handler, the
		 *       download will pause until the event handler returns.
		 */
		@Override
		public void onRecvHeader(int iHttpStatus, int iExpectedLength, String strMIME, String strEncoding)
		{
		}

		/**
		 * onDownloadProgress
		 * 
		 * @note callback when download progress changed
		 * 
		 * @param iNowSize
		 *            [in]: size until now
		 */
		@Override
		public void onDownloadProgress(String strFileName, int fileNowSize)
		{
		}
	}
}
